﻿''' <summary>
''' An in-memory cache for the add-in settings. The app may need to un-deploy/re-deploy to recycle IIS to clear this in some cases.
''' </summary>
''' <remarks></remarks>
Public Class CachedSettings
    ''' <summary>
    ''' Singleton
    ''' </summary>
    ''' <remarks></remarks>
    Private Shared m_Instance As CachedSettings

    Private Shared m_Lock As New Object

    Private Shared m_ReloadInterval As Integer = 60
    Private Shared m_LastLoadTime As DateTime = DateTime.MinValue

    ''' <summary>
    ''' Singleton implementation
    ''' </summary>
    ''' <param name="pSettingsFile"></param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Shared Function GetInstance(ByVal pSettingsFile As String) As CachedSettings
        SyncLock m_Lock
            If m_Instance Is Nothing OrElse DateTime.Now.Subtract(m_LastLoadTime) > New TimeSpan(0, 0, m_ReloadInterval) Then
                m_Instance = New CachedSettings(pSettingsFile)
                m_LastLoadTime = DateTime.Now
            End If
            m_ReloadInterval = m_Instance.Settings.ReloadInterval
            Return m_Instance
        End SyncLock
    End Function

    Private m_Settings As Settings
    Public Property Settings() As Settings
        Get
            Return m_Settings
        End Get
        Set(ByVal value As Settings)
            m_Settings = value
        End Set
    End Property

    Public Sub New(ByVal pSettingsFile As String)
        m_Settings = New Settings(pSettingsFile)
    End Sub
End Class

''' <summary>
''' For loading and persisting the settings for the add-in
''' </summary>
''' <remarks></remarks>
Public Class Settings
    Private m_Url As String
    Private m_AppID As String
    Private m_AppKey As String
    Private m_IsDeployed As Boolean
    Private m_Users As New Generic.Dictionary(Of String, String)
    Private m_ReloadInterval As Integer
    Private m_EnableAuthenticationLogging As Boolean
    Private m_LogFileName As String

    ''' <summary>
    ''' The url for the yubikey validation service
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Property Url() As String
        Get
            Return m_Url
        End Get
        Set(ByVal value As String)
            m_Url = value
        End Set
    End Property

    ''' <summary>
    ''' The appid for this client of the yubikey validation service.
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Property AppID() As String
        Get
            Return m_AppID
        End Get
        Set(ByVal value As String)
            m_AppID = value
        End Set
    End Property

    ''' <summary>
    ''' The shared key for this client of the yuibikey validation service.
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Property AppKey() As String
        Get
            Return m_AppKey
        End Get
        Set(ByVal value As String)
            m_AppKey = value
        End Set
    End Property

    ''' <summary>
    ''' Are we deployed or undeployed.
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Property IsDeployed() As Boolean
        Get
            Return m_IsDeployed
        End Get
        Set(ByVal value As Boolean)
            m_IsDeployed = value
        End Set
    End Property

    ''' <summary>
    ''' The public ids of all the users we are allowing remote access logons for.
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Property Users() As Generic.Dictionary(Of String, String)
        Get
            Return m_Users
        End Get
        Set(ByVal value As Generic.Dictionary(Of String, String))
            m_Users = value
        End Set
    End Property

    ''' <summary>
    ''' How often should we reload the settings from disk.
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Property ReloadInterval() As Integer
        Get
            Return m_ReloadInterval
        End Get
        Set(ByVal value As Integer)
            m_ReloadInterval = value
        End Set
    End Property

    ''' <summary>
    ''' Enable logging of authentication failures.
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Property EnableAuthenticationLogging() As Boolean
        Get
            Return m_EnableAuthenticationLogging
        End Get
        Set(ByVal value As Boolean)
            m_EnableAuthenticationLogging = value
        End Set
    End Property

    Private m_Filename As String

    ''' <summary>
    ''' Persist a setting to xml.
    ''' </summary>
    ''' <param name="pDoc"></param>
    ''' <param name="pRoot"></param>
    ''' <param name="pEleName"></param>
    ''' <param name="pEleValue"></param>
    ''' <remarks></remarks>
    Protected Sub PersistSetting(ByVal pDoc As Xml.XmlDocument, ByVal pRoot As Xml.XmlElement, ByVal pEleName As String, ByVal pEleValue As String)
        Dim _ele As Xml.XmlElement = pDoc.CreateElement(pEleName)
        _ele.InnerText = pEleValue
        pRoot.AppendChild(_ele)
    End Sub

    ''' <summary>
    ''' Persist the settings to a file.
    ''' </summary>
    ''' <remarks></remarks>
    Public Sub Save()
        Dim _doc As New Xml.XmlDocument
        Dim _root As Xml.XmlElement = _doc.CreateElement("Settings")
        _doc.AppendChild(_root)
        PersistSetting(_doc, _root, "Url", Url)
        PersistSetting(_doc, _root, "AppID", AppID)
        PersistSetting(_doc, _root, "AppKey", AppKey)
        PersistSetting(_doc, _root, "IsDeployed", IsDeployed.ToString)
        PersistSetting(_doc, _root, "ReloadInterval", ReloadInterval.ToString)
        PersistSetting(_doc, _root, "EnableAuthenticationLogging", EnableAuthenticationLogging.ToString)

        Dim _users As Xml.XmlElement = _doc.CreateElement("Users")
        _root.AppendChild(_users)
        For Each _user As String In Users.Keys
            Dim _userEle As Xml.XmlElement = _doc.CreateElement("User")
            _users.AppendChild(_userEle)
            _userEle.SetAttribute("Name", _user)
            _userEle.SetAttribute("PublicID", Users.Item(_user))
        Next

        _doc.Save(m_Filename)
    End Sub

    ''' <summary>
    ''' Load a settings element from the xml settings.
    ''' </summary>
    ''' <param name="pRoot"></param>
    ''' <param name="pSettingName"></param>
    ''' <param name="pDefaultValue"></param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Protected Function LoadSetting(ByVal pRoot As Xml.XmlElement, ByVal pSettingName As String, ByVal pDefaultValue As String) As String
        For Each _ele As Xml.XmlNode In pRoot.ChildNodes
            If _ele.NodeType = Xml.XmlNodeType.Element Then
                If String.Compare(_ele.Name, pSettingName, True) = 0 Then
                    Return _ele.InnerText
                End If
            End If
        Next
        Return pDefaultValue
    End Function

    ''' <summary>
    ''' Find the users xml node.
    ''' </summary>
    ''' <param name="pEle"></param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Protected Function GetUsersNode(ByVal pEle As Xml.XmlElement) As Xml.XmlElement
        For Each _ele As Xml.XmlNode In pEle.ChildNodes
            If _ele.NodeType = Xml.XmlNodeType.Element Then
                If String.Compare(_ele.Name, "Users", True) = 0 Then
                    Return CType(_ele, Xml.XmlElement)
                End If
            End If
        Next
        Return Nothing
    End Function

    ''' <summary>
    ''' Log an error
    ''' </summary>
    ''' <param name="pError"></param>
    ''' <remarks></remarks>
    Public Sub LogError(ByVal pError As String)
        'TODO: Buffering
        Dim _sr As New IO.StreamWriter(m_LogFileName, True)
        _sr.WriteLine(pError)
        _sr.Flush()
        _sr.Close()
    End Sub

    ''' <summary>
    ''' Log an error if authentication failure logging is enabled.
    ''' </summary>
    ''' <param name="pError"></param>
    ''' <remarks></remarks>
    Public Sub LogErrorIfEnabled(ByVal pError As String)
        If EnableAuthenticationLogging Then
            LogError(pError)
        End If
    End Sub

    ''' <summary>
    ''' Read all errors
    ''' </summary>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Function ErrorText() As String
        'TODO: Buffering
        If IO.File.Exists(m_LogFileName) Then
            Return IO.File.ReadAllText(m_LogFileName)
        Else
            Return ""
        End If
    End Function

    ''' <summary>
    ''' Clear the errors
    ''' </summary>
    ''' <remarks></remarks>
    Public Sub ClearErrors()
        If IO.File.Exists(m_LogFileName) Then IO.File.Delete(m_LogFileName)
    End Sub

    ''' <summary>
    ''' Load the settings from the given file name.
    ''' </summary>
    ''' <param name="pFileName"></param>
    ''' <remarks></remarks>
    Public Sub New(ByVal pFileName As String)
        m_Filename = pFileName
        m_LogFileName = IO.Path.Combine(IO.Path.GetDirectoryName(m_Filename), "ErrorLog.txt")

        'Set default values.
        Url = "https://api.yubico.com/wsapi/verify?"
        AppID = ""
        AppKey = ""
        IsDeployed = False
        ReloadInterval = 60
        EnableAuthenticationLogging = False

        If IO.File.Exists(m_Filename) Then
            Dim _doc As New Xml.XmlDocument()
            _doc.Load(m_Filename)

            Url = LoadSetting(_doc.DocumentElement, "Url", "https://api.yubico.com/wsapi/verify?")
            AppID = LoadSetting(_doc.DocumentElement, "AppID", "")
            AppKey = LoadSetting(_doc.DocumentElement, "AppKey", "")
            Dim _isDeployed As String = LoadSetting(_doc.DocumentElement, "IsDeployed", "False")
            If _isDeployed IsNot Nothing Then Boolean.TryParse(_isDeployed, IsDeployed)
            Dim _reloadInterval As String = LoadSetting(_doc.DocumentElement, "ReloadInterval", "60")
            If _reloadInterval IsNot Nothing Then Integer.TryParse(_reloadInterval, ReloadInterval)
            Dim _enableAuthenticationLogging As String = LoadSetting(_doc.DocumentElement, "EnableAuthenticationLogging", "False")
            If _enableAuthenticationLogging IsNot Nothing Then Boolean.TryParse(_enableAuthenticationLogging, EnableAuthenticationLogging)

            Dim _users As Xml.XmlElement = GetUsersNode(_doc.DocumentElement)
            If _users IsNot Nothing Then
                For Each _ele As Xml.XmlNode In _users.ChildNodes
                    If _ele.NodeType = Xml.XmlNodeType.Element Then
                        If String.Compare(_ele.Name, "User", True) = 0 Then
                            Dim _element As Xml.XmlElement = CType(_ele, Xml.XmlElement)
                            If _element.HasAttribute("Name") AndAlso _element.HasAttribute("PublicID") Then
                                Users.Add(_element.GetAttribute("Name"), _element.GetAttribute("PublicID"))
                            End If
                        End If
                    End If
                Next
            End If
        End If

    End Sub
End Class